package com.gf.framework.web.aspect;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.gf.framework.common.utils.CaffeineCacheMap;
import com.gf.framework.web.annotation.MemoryCache;
import com.gf.framework.web.annotation.MemoryCacheClear;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author gaofei
 * @date 2023/7/30 8:48
 */
@Slf4j
@Aspect
@Order(-1)
@Component
public class MemoryCacheAspect {
    @Resource
    private ObjectMapper objectMapper;

    private static class MemoryCacheMap {
        private final Map<String, Map<String, Object>> CACHE = new ConcurrentHashMap<>();

        /**
         * 存缓存
         * @param type 数据类型
         * @param maxSize 最大容量
         * @param key key
         * @param value 值
         */
        public void put(String type, String key, Object value, int maxSize, long expire, ChronoUnit chronoUnit) {
            if (!CACHE.containsKey(type)) {
                synchronized (this) {
                    if (!CACHE.containsKey(type)) {
                        Map<String, Object> map = CaffeineCacheMap.createTimeOut(maxSize, expire, chronoUnit);
                        CACHE.put(type, map);
                    }
                }
            }
            Map<String, Object> map = CACHE.get(type);
            synchronized (map) {
                map.put(key, value);
            }
        }

        public Object get(String type, String key) {
            Map<String, Object> map = CACHE.get(type);
            if (Objects.isNull(map)) {
                return null;
            }
            return map.get(key);
        }

        public void clear(String type) {
            Map<String, Object> map = CACHE.get(type);
            if (Objects.nonNull(map)) {
                synchronized (map) {
                    map.clear();
                }
            }
        }
    }

    private static final MemoryCacheMap CACHE_MAP = new MemoryCacheMap();

    @Around("@annotation(com.gf.framework.web.annotation.MemoryCache)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        MemoryCache memoryCache = method.getAnnotation(MemoryCache.class);

        try {
            Object[] args = joinPoint.getArgs();
            String paramStr = Stream.of(args).map(o -> {
                if (Objects.isNull(o)) {
                    return "null";
                }
                try {
                    return objectMapper.writeValueAsString(o);
                } catch (Exception e) {
                    return o.toString();
                }
            }).collect(Collectors.joining(",", "(", ")"));
            String key = String.format("%s:%s:%s", joinPoint.getSignature().toString(),
                    method.getName(), paramStr);
            Object obj = CACHE_MAP.get(memoryCache.type(), key);
            if (Objects.isNull(obj)) {
                obj = joinPoint.proceed();
                if (Objects.nonNull(obj)) {
                    CACHE_MAP.put(memoryCache.type(), key, obj, memoryCache.maxSize(),
                            memoryCache.expire(), memoryCache.chronoUnit());
                }
            }
            return obj;
        }
        catch (Throwable throwable) {
            throw throwable;
        }
        finally {
        }
    }

    @After("@annotation(com.gf.framework.web.annotation.MemoryCacheClear)")
    public void after(JoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        MemoryCacheClear memoryCacheClear = method.getAnnotation(MemoryCacheClear.class);
        for (String type : memoryCacheClear.type()) {
            CACHE_MAP.clear(type);
        }
    }
}
